/* Demonstration program of reading packet trace files recorded by pcap
 * (used by tshark and tcpdump) and dumping out some corresponding information
 * in a human-readable form.
 *
 * Note, this program is limited to processing trace files that contains
 * UDP packets.  It prints the timestamp, source port, destination port,
 * and length of each such packet.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <fcntl.h>

#include <netinet/in.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <netinet/if_ether.h>

#include <pcap.h>

#include "stdGNUTDefs.h"
#include "Packet.h"

using namespace std;

/* We've included the UDP header struct for your ease of customization.
 * For your protocol, you might want to look at netinet/tcp.h for hints
 * on how to deal with single bits or fields that are smaller than a byte
 * in length.
 *
 * Per RFC 768, September, 1981.
 */
struct UDP_hdr {
	u_short	uh_sport;		/* source port */
	u_short	uh_dport;		/* destination port */
	u_short	uh_ulen;		/* datagram length */
	u_short	uh_sum;			/* datagram checksum */
};


/* Some helper functions, which we define at the end of this file. */

/* Returns a string representation of a timestamp. */
const char *timestamp_string(struct timeval ts);

/* Report a problem with dumping the packet with the given timestamp. */
void problem_pkt(struct timeval ts, const char *reason);

/* Report the specific problem of a packet being too short. */
void too_short(struct timeval ts, const char *truncated_hdr);

/* dump_UDP_packet()
 *
 * This routine parses a packet, expecting Ethernet, IP, and UDP headers.
 * It extracts the UDP source and destination port numbers along with the UDP
 * packet length by casting structs over a pointer that we move through
 * the packet.  We can do this sort of casting safely because libpcap
 * guarantees that the pointer will be aligned.
 *
 * The "ts" argument is the timestamp associated with the packet.
 *
 * Note that "capture_len" is the length of the packet *as captured by the
 * tracing program*, and thus might be less than the full length of the
 * packet.  However, the packet pointer only holds that much data, so
 * we have to be careful not to read beyond it.
 */
void dump_UDP_packet(const unsigned char *packet, struct timeval ts,
		unsigned int capture_len, int fd)
{
	struct ip *ip;
	struct UDP_hdr *udp;
	unsigned int IP_header_length;

	/* For simplicity, we assume Ethernet encapsulation. */

	if (capture_len < sizeof(struct ether_header))
	{
		/* We didn't even capture a full Ethernet header, so we
		 * can't analyze this any further.
		 */
		too_short(ts, "Ethernet header");
		return;
	}

	/* Skip over the Ethernet header. */
	packet += sizeof(struct ether_header);
	capture_len -= sizeof(struct ether_header);

	if (capture_len < sizeof(struct ip))
	{ /* Didn't capture a full IP header */
		too_short(ts, "IP header");
		return;
	}

	ip = (struct ip*) packet;
	IP_header_length = ip->ip_hl * 4;	/* ip_hl is in 4-byte words */

	if (capture_len < IP_header_length)
	{ /* didn't capture the full IP header including options */
		too_short(ts, "IP header with options");
		return;
	}

	if (ip->ip_p != IPPROTO_UDP)
	{
		problem_pkt(ts, "non-UDP packet");
		return;
	}

	/* Skip over the IP header to get to the UDP header. */
	packet += IP_header_length;
	capture_len -= IP_header_length;

	if (capture_len < sizeof(struct UDP_hdr))
	{
		too_short(ts, "UDP header");
		return;
	}

	/* TODO: are we supposed to skip over this? */
	udp = (struct UDP_hdr*) packet;

	printf("%s UDP src_port=%d dst_port=%d length=%d\n",
			timestamp_string(ts),
			ntohs(udp->uh_sport),
			ntohs(udp->uh_dport),
			ntohs(udp->uh_ulen));

	/* Skip over the UDP header to get to the GNUT header. */
	packet += sizeof(struct UDP_hdr);
	capture_len -= sizeof(struct UDP_hdr);

	if (capture_len < sizeof(GNUT_Header_t))
	{
		too_short(ts, "GNUT header");
		return;
	}

	// at this stage we can go ahead and parse the GNUT Header
	// the packet class can help us a lot with this
	Packet gnut_pckt = Packet((char*)packet, capture_len);
	char cvsLine[128];

	sprintf(cvsLine, "%s,%d,%d,%d,%d,%d,%d\n", 	timestamp_string(ts),
													gnut_pckt.getSrcPort(), 
													gnut_pckt.getDstPort(), 
													gnut_pckt.getSeqNo(), 
													gnut_pckt.getAdvWindow(), 
													gnut_pckt.getType(),
													gnut_pckt.getSize()	);

	if( write(fd, cvsLine, strlen(cvsLine)) == -1 )
	{
		printf("failed to write entry\n");
		exit(1);
	}
}


int main(int argc, char *argv[])
{
	pcap_t *pcap;
	const unsigned char *packet;
	char errbuf[PCAP_ERRBUF_SIZE];
	struct pcap_pkthdr header;

	/* Skip over the program name. */
	++argv; --argc;

	/* We expect exactly two arguments, the input file and the output file. */
	if ( argc != 2 )
	{
		fprintf(stderr, "program requires two arguments, the input trace and the output .csv file\n");
		exit(1);
	}

	/* Try opening input file */
	pcap = pcap_open_offline(argv[0], errbuf);
	if (pcap == NULL)
	{
		fprintf(stderr, "error reading pcap file: %s\n", errbuf);
		exit(1);
	}

	/* Try opening the output file */
	char* filename = argv[1];
	int fd;

	string path("./");
	path.append(filename);

	verbose ? printf("Opening output file: %s\n", path.c_str()):1;
	if( (fd = creat(path.c_str(), S_IRWXU)) == -1 )
	{
		printf("failed to open file\n");
		exit(1);
	}

	// Write label header to the file
	if( write(fd, "Time,srcPort,dstPort,SeqAckNo,AdvWindow,Type,Size\n", strlen("Time,srcPort,dstPort,SeqAckNo,AdvWindow,Type,Size\n")) == -1 )
	{
		printf("failed to write label\n");
		exit(1);
	}

	/* Now just loop through extracting packets as long as we have
	 * some to read.
	 */
	while ((packet = pcap_next(pcap, &header)) != NULL)
		dump_UDP_packet(packet, header.ts, header.caplen, fd);

	// terminate
	return 0;
}


/* Note, this routine returns a pointer into a static buffer, and
 * so each call overwrites the value returned by the previous call.
 */
const char *timestamp_string(struct timeval ts)
{
	static char timestamp_string_buf[256];

	sprintf(timestamp_string_buf, "%d.%06d",
			(int) ts.tv_sec, (int) ts.tv_usec);

	return timestamp_string_buf;
}

void problem_pkt(struct timeval ts, const char *reason)
{
	fprintf(stderr, "%s: %s\n", timestamp_string(ts), reason);
}

void too_short(struct timeval ts, const char *truncated_hdr)
{
	fprintf(stderr, "packet with timestamp %s is truncated and lacks a full %s\n",
			timestamp_string(ts), truncated_hdr);
}
